use crate::low_energy_client::{HandleData, LowEnergyClient, LowEnergyClientOption};
use alloc::boxed::Box;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use core::clone::Clone;
use core::marker::Copy;
use core::prelude::rust_2018::derive;
use core::ptr;
use embed_std::util::cstr_to_str;
use embed_std::{debugln, errorln, infoln};

pub const low_energy_status_e_LE_STATUS_NORMAL: low_energy_status_e = 0;
pub const low_energy_status_e_LE_STATUS_SLEEPING: low_energy_status_e = 1;
pub const low_energy_status_e_LE_STATUS_WAKING: low_energy_status_e = 2;
pub const low_energy_status_e_LE_STATUS_WOKE: low_energy_status_e = 3;
pub const low_energy_status_e_LE_STATUS_TO_WAKE: low_energy_status_e = 4;
pub type low_energy_status_e = ::core::ffi::c_uint;

#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct low_energy_client {
    pub priv_: *mut ::core::ffi::c_void,
    pub free: Option<unsafe extern "C" fn(self_: *mut low_energy_client)>,
    #[doc = " @return 0(ok), !0(fail)"]
    pub init: Option<unsafe extern "C" fn(self_: *mut low_energy_client) -> ::core::ffi::c_int>,
    #[doc = " @return 0(ok), !0(fail)"]
    pub report: Option<
        unsafe extern "C" fn(
            self_: *mut low_energy_client,
            status: low_energy_status_e,
        ) -> ::core::ffi::c_int,
    >,
    pub handle_msg: Option<
        unsafe extern "C" fn(
            self_: *mut low_energy_client,
            to_in_ms: core::ffi::c_uint,
        ) -> ::core::ffi::c_int,
    >,
}

pub const low_energy_event_e_LOW_ENERGY_EVENT_AWAKE: low_energy_event_e = 0;
pub type low_energy_event_e = ::core::ffi::c_uint;
pub type low_energy_event_func_t = Option<unsafe extern "C" fn(type_: low_energy_event_e)>;

#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct low_energy_client_option_t {
    pub servers: [::core::ffi::c_char; 128usize],
    pub device_uid: [::core::ffi::c_char; 64usize],
    pub product_code: [::core::ffi::c_char; 64usize],
    pub secret: [::core::ffi::c_char; 64usize],
    pub event_cb: low_energy_event_func_t,
}

impl From<low_energy_client_option_t> for LowEnergyClientOption {
    fn from(value: low_energy_client_option_t) -> Self {
        let addrs: Vec<String> = cstr_to_str(value.servers.as_ptr())
            .split(";")
            .map(|v| v.to_string())
            .collect();
        LowEnergyClientOption {
            addrs,
            product_code: cstr_to_str(value.product_code.as_ptr()).to_string(),
            device_uid: cstr_to_str(value.device_uid.as_ptr()).to_string(),
            secret: cstr_to_str(value.secret.as_ptr()).to_string(),
            handler: Some(Box::new(LeHandler::new(value.event_cb))),
        }
    }
}

struct LeHandler {
    cb: low_energy_event_func_t,
}

impl LeHandler {
    pub fn new(cb: low_energy_event_func_t) -> Self {
        Self { cb }
    }
}

impl HandleData for LeHandler {
    fn handle(&mut self, payload: &[u8], is_req: bool) {
        let data = unsafe { core::str::from_utf8_unchecked(payload) };
        debugln!("{} is_req {}", data, is_req);
        if data.find("awake").is_some() {
            infoln!("got awake data: {}", data);
            if let Some(cb) = self.cb {
                unsafe { cb(low_energy_event_e_LOW_ENERGY_EVENT_AWAKE) };
            }
        }
    }
}

#[no_mangle]
pub unsafe extern "C" fn create_le_client(
    opt: *mut low_energy_client_option_t,
) -> *mut low_energy_client {
    debugln!("opt {:p}", opt);
    if opt.is_null() {
        return ptr::null_mut();
    }
    let opts = LowEnergyClientOption::from(*opt);
    let client = Box::new(LowEnergyClient::new(opts));
    let wrapper = Box::new(low_energy_client {
        priv_: Box::into_raw(client) as *mut core::ffi::c_void,
        free: Some(free_le_client),
        init: Some(init_le_client),
        report: Some(report_le_client),
        handle_msg: Some(handle_msg_le_client),
    });
    Box::into_raw(wrapper)
}

unsafe extern "C" fn free_le_client(self_: *mut low_energy_client) {
    debugln!("self {:p}", self_);
    if self_.is_null() {
        return;
    }
    let w = Box::from_raw(self_);
    if w.priv_.is_null() {
        return;
    }
    let client = Box::from_raw(w.priv_ as *mut LowEnergyClient);
    drop(client);
}

unsafe extern "C" fn init_le_client(self_: *mut low_energy_client) -> core::ffi::c_int {
    debugln!("self {:p}", self_);
    if self_.is_null() {
        return -1;
    }
    let w = Box::leak(Box::from_raw(self_));
    if w.priv_.is_null() {
        return -1;
    }
    let client = Box::leak(Box::from_raw(w.priv_ as *mut LowEnergyClient));
    match client.init() {
        Ok(_) => 0,
        Err(e) => {
            errorln!("init client failed: {}", e);
            -2
        }
    }
}

unsafe extern "C" fn report_le_client(
    self_: *mut low_energy_client,
    status: low_energy_status_e,
) -> core::ffi::c_int {
    // debugln!("self {:p}", self_);
    if self_.is_null() {
        return -1;
    }
    let w = Box::leak(Box::from_raw(self_));
    if w.priv_.is_null() {
        return -1;
    }
    let client = Box::leak(Box::from_raw(w.priv_ as *mut LowEnergyClient));
    match client.report(status as i32) {
        Ok(_) => 0,
        Err(e) => {
            errorln!("send report client failed: {}", e);
            -2
        }
    }
}

unsafe extern "C" fn handle_msg_le_client(
    self_: *mut low_energy_client,
    to_in_ms: core::ffi::c_uint,
) -> core::ffi::c_int {
    // debugln!("self {:p}", self_);
    if self_.is_null() {
        return -1;
    }
    let w = Box::leak(Box::from_raw(self_));
    if w.priv_.is_null() {
        return -1;
    }
    let client = Box::leak(Box::from_raw(w.priv_ as *mut LowEnergyClient));
    match client.handle(to_in_ms) {
        Ok(_) => 0,
        Err(e) => {
            errorln!("handle msg failed: {}", e);
            -2
        }
    }
}
